#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/types.h>
#include <linux/kdev_t.h>
#include <linux/device.h>
#include <linux/uaccess.h>

MODULE_LICENSE("GPL");

static int minor = 0; /* 次设备号 */
static dev_t csdn_dev;
static struct cdev csdn_cdev;
static struct class *csdn_class = NULL;
static struct device *csdn_device = NULL;

static int csdn_open(struct inode *inode, struct file *filp)
{
    /* TODO */
    return 0;
}

static int csdn_release(struct inode *inode, struct file *filp)
{
    /* TODO */
    return 0;
}

static ssize_t csdn_read(struct file *filp, char __user *buf, size_t count, loff_t *f_pos)
{
    /* TODO */
    return 0;
}

static ssize_t csdn_write(struct file *filp, const char __user *buf, size_t count, loff_t *f_pos)
{
    /* TODO */
    return 0;
}

static struct file_operations csdn_fops = {
    .owner = THIS_MODULE,
    .open = csdn_open,
    .release = csdn_release,
    .read = csdn_read,
    .write = csdn_write,
};

/* 创建设备号 */
static int csdn_register_chrdev(void)
{
    int result;

    result = alloc_chrdev_region(&csdn_dev, minor, 1, "csdn_dev");
    if (result < 0) {
        pr_err("alloc_chrdev_region failed! result: %d\n", result);
        return result;
    }

    return 0;
}

/* 注册驱动 */
static int csdn_cdev_add(void)
{
    int result;

    cdev_init(&csdn_cdev, &csdn_fops);
    csdn_cdev.owner = THIS_MODULE;

    result = cdev_add(&csdn_cdev, csdn_dev, 1);
    if (result < 0) {
        pr_err("alloc_chrdev_region failed! result: %d\n", result);
        unregister_chrdev_region(csdn_dev, 1);
        return result;
    }

    return 0;
}

/* 创建设备节点 */
static int csdn_device_create(void)
{
    csdn_class = class_create(THIS_MODULE, "csdn_dev_class");
    if (IS_ERR(csdn_class)) {
        pr_err("class_create failed!\n");
        goto class_create_fail;
    }

    csdn_device = device_create(csdn_class, NULL, csdn_dev, NULL, "csdn_dev");
    if (IS_ERR(csdn_device)) {
        pr_err("device_create failed!\n");
        goto device_create_fail;
    }

    return 0;

device_create_fail:
    class_destroy(csdn_class);
class_create_fail:
    cdev_del(&csdn_cdev);
    unregister_chrdev_region(csdn_dev, 1);
    return -1;
}

static ssize_t csdn_sysfs_read_set(struct device *dev,
	struct device_attribute *attr, char *buf)
{
    char *data =  "csdn_sysfs_read_set";
    int data_len = strlen("csdn_sysfs_read_set");
    pr_info("csdn_sysfs_read_set\n");

    sprintf(buf, data);

    return data_len;
}
static DEVICE_ATTR(csdn_sysfs_read, S_IRUSR, csdn_sysfs_read_set, NULL);

static ssize_t csdn_sysfs_write_set(struct device *dev,
	struct device_attribute *attr, const char *buf, size_t count)
{
    if (!strncmp(buf, "enable", strlen("enable"))) {
        pr_info("csdn_sysfs_write_set enable\n");
    } else if (!strncmp(buf, "disable", strlen("disable"))) {
        pr_info("csdn_sysfs_write_set disable\n");
    } else {
        pr_info("csdn_sysfs_write_set error!\n");
    }

    return count;
}
static DEVICE_ATTR(csdn_sysfs_write, S_IWUSR, NULL, csdn_sysfs_write_set);

static struct attribute *csdn_attributes[] = {
    &dev_attr_csdn_sysfs_read.attr,
    &dev_attr_csdn_sysfs_write.attr,
	NULL
};

static const struct attribute_group csdn_attribute_group = {
	.attrs = csdn_attributes,
};

static __init int csdn_init(void)
{
    int result;

    result = csdn_register_chrdev();
    if (result < 0) {
        return result;
    }

    result = csdn_cdev_add();
    if (result < 0) {
        return result;
    }

    result = csdn_device_create();
    if (result < 0) {
        return result;
    }

    result = sysfs_create_group(&csdn_device->kobj, &csdn_attribute_group);
    if (result) {
        device_destroy(csdn_class, csdn_dev);
        class_destroy(csdn_class);
        cdev_del(&csdn_cdev);
        unregister_chrdev_region(csdn_dev, 1);
        return result;
    }

    return 0;
}

static __exit void csdn_exit(void)
{
    sysfs_remove_group(&csdn_device->kobj, &csdn_attribute_group);
    device_destroy(csdn_class, csdn_dev);
    class_destroy(csdn_class);
    cdev_del(&csdn_cdev);
    unregister_chrdev_region(csdn_dev, 1);
}

module_init(csdn_init);
module_exit(csdn_exit);